package com.cfpamf.ms.insur.wx.client;

import com.cfpamf.ms.insur.wx.exception.WxPayException;
import com.cfpamf.ms.insur.wx.model.SignatureItem;
import com.cfpamf.ms.insur.wx.model.WxPayModel;
import com.cfpamf.ms.insur.wx.request.WechatPayDownloadRequest;
import com.cfpamf.ms.insur.wx.request.WechatPayRequest;
import com.cfpamf.ms.insur.wx.response.WxPayResponse;
import com.cfpamf.ms.insur.wx.util.WXPayUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.KeyStore;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;

import static com.cfpamf.ms.insur.wx.util.WXPayConstants.SignType;


/**
 * 微信支付， 分装的客户端， 对request自动加上签名，转成xml， 发送请求到微信支付，  会返回结果做解析
 */
@Builder
@Slf4j
@Data
public class WechatPayClient {

    private static final String SERVER_URL = "https://api.mch.weixin.qq.com";
    @Getter
    private ObjectMapper mapper;
    private String appId; // 微信为公众账号Id 或服务商ID
    private String mchId; // 微信支付 商户号
    private String apiKey;// 秘钥，用于签名

    private byte[] certFile;// 退款时候 数字证书

    /**
     * 执行默认的查询
     *
     * @param request
     * @param <T>
     * @return
     */
    public <T extends WxPayResponse> T execute(WechatPayRequest<? extends WxPayModel, T> request) {

        //默认的解析
        ResponseHandler<T> handler = response -> {
            StringBuilder builder = new StringBuilder();
            HttpEntity entity = response.getEntity();
            String temp;
            if (entity != null) {
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));
                while ((temp = bufferedReader.readLine()) != null) {
                    builder.append(temp);
                }
            }
            String respContent = builder.toString();
            log.info("wx request {}==>\n{}", request.getApiAction(), respContent);
            WXPayUtil.signatureValid(respContent, apiKey, request.getSignType());
            log.info("校验响应数据成功");
            log.info("wx request {}==>\n{}", request.getApiAction(), respContent);
            T result = mapper.readValue(respContent, request.getResponseClass());
            result.setContent(respContent);
            return result;
        };
        // 如果下载请求
        if (request instanceof WechatPayDownloadRequest) {
            handler = ((WechatPayDownloadRequest<? extends WxPayModel, T>) request).getResponseHandler(mapper);
        }
        return post(SERVER_URL + request.getApiAction(), request, handler);
    }


//    //执行下载操作
//    public <T extends WxPayResponse> T executeDownload(WechatPayDownloadRequest<? extends WxPayModel, T> request) {
//        return post(SERVER_URL + request.getApiAction(), request, request.getResponseHandler());
//    }

    public String sign(Map<String, String> data, SignType signType) {
        return WXPayUtil.generateSignature(data, apiKey, signType);
    }

    /**
     * 获取 返回值中的sign，以及计算sign的字符串
     *
     * @param rsp
     * @return
     */
    private SignatureItem getSignatureItem(String rsp) throws IOException {


        Map<String, String> data = mapper.readValue(rsp, Map.class);
        if (data == null || data.isEmpty()) {
            return null;
        }
        if (!data.containsKey("sign")) {
            return null;
        }
        SignatureItem signatureItem = new SignatureItem();
        signatureItem.setSign(String.valueOf(data.get("sign")));
        signatureItem.setSignContent(sign(data, SignType.valueOf(data.get("sign_type") + "")));
        return signatureItem;
    }


    /**
     * 校验参数是否正确
     *
     * @param content
     */
    public void vaildParams(String content) {
        vaildParams(content, SignType.MD5);
    }

    /**
     * 校验参数是否正确
     *
     * @param content
     */
    public void vaildParams(String content, SignType signType) {
        WXPayUtil.signatureValid(content, apiKey, signType);
    }

    /**
     * 根据返回值 解析成responseModel，校验签名
     *
     * @param clazz
     * @param rsp
     * @param <T>
     * @return
     * @throws WxPayException
     */
    public <T extends WxPayResponse> T parse(Class<T> clazz, String rsp) throws IOException {
        T response = mapper.readValue(rsp, clazz);

        SortedMap<String, String> convert = convert(response);
        if (response == null) {
            throw new WxPayException("微信支付 解析结果失败!");
        }

        SignatureItem signItem = getSignatureItem(rsp);
        if (signItem == null) {
            throw new WxPayException(response.toString());
        }

        if (response.isSuccess() || (!response.isSuccess() && !StringUtils.isEmpty(signItem.getSign()))) {
            boolean checkContent = WXPayUtil.isSignatureValid(convert, this.apiKey);
            if (!checkContent) {
                throw new WxPayException("sign check fail: check Sign and Data Fail!");
            }
        }
        return response;
    }

    /**
     * 发起微信支付请求
     *
     * @param url
     * @param request
     * @param <T>
     * @return
     * @throws WxPayException
     */
    private <T extends WxPayResponse, R> R post(String url,
                                                WechatPayRequest<? extends WxPayModel, T> request,
                                                ResponseHandler<R> handler) {
        WxPayModel model = request.getModel();

        model.setAppId(appId);
        model.setMchId(mchId);
//        if (this.isServer()) {
//            model.setSubAppid(subAppid);
//            model.setSubMchId(subMchId);
//        }
        model.setNonceStr(UUID.randomUUID().toString().replace("-", ""));

        SortedMap<String, String> data = convert(model);
        String sign = sign(data, request.getSignType());
        model.setSign(sign);
        data.put("sign", sign);

        R text;
        //判断是否需要带上 支付证书
        try (CloseableHttpClient client = this.getClient(request)) {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded");
            String bodyString = mapper.writeValueAsString(data);
            bodyString = bodyString.replaceAll("(<)(/?)(TreeMap>)", "<$2xml>");
            log.info("{} req body:{}", request.getApiAction(), bodyString);
            //替换掉
            StringEntity payload = new StringEntity(bodyString, "UTF-8");
            httpPost.setEntity(payload);
            text = client.execute(httpPost, handler);
        } catch (Exception e) {
            throw new WxPayException(e.getMessage(), e);
        }
        return text;
    }

    /**
     * 是否为服务商模式
     *
     * @return
     */
    public boolean isServer() {
        return false;
//        return StringUtils.isNotBlank(subAppid);
    }

    public SortedMap<String, String> convert(Object model) {
        Map<String, Object> data = mapper.convertValue(model, Map.class);
        SortedMap<String, String> params = new TreeMap<>();
        data.forEach((key, value) -> params.put(key, String.valueOf(value)));
        return params;
    }

    public String getDomain() {

        //微信容灾
        return SERVER_URL;
    }


    //判断是否需要带上 支付证书
    private <T extends WxPayResponse> String request(WechatPayRequest<? extends WxPayModel, T> request) throws IOException {

        CloseableHttpClient client = getClient(request);
        String url = getDomain() + request.getApiAction();
        HttpPost httpPost = new HttpPost(url);
        WxPayModel model = request.getModel();
        model.setNonceStr(UUID.randomUUID().toString().replace("-", ""));
        SortedMap<String, String> data = convert(request.getModel());
        log.info("wechat.pay.url={},request={}", url, data);
        String sign = sign(data, request.getSignType());
        model.setSign(sign);
        data.put("sign", sign);
        String bodyString = mapper.writeValueAsString(data);
        // 将TreeMap 替换成xml 这一步其实不做也可以请求成功 微信并没有做根节点的解析
        bodyString = bodyString.replaceAll("(<)(/?)(TreeMap>)", "<$2xml>");

        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10 * 1000).setConnectTimeout(10 * 1000).build();
        httpPost.setConfig(requestConfig);
        StringEntity postEntity = new StringEntity(bodyString, "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.setEntity(postEntity);
        HttpResponse httpResponse = client.execute(httpPost);
        HttpEntity httpEntity = httpResponse.getEntity();
        return EntityUtils.toString(httpEntity);
    }

    private <T extends WxPayResponse> CloseableHttpClient getClient(WechatPayRequest<? extends WxPayModel, T>
                                                                                request) {
        CloseableHttpClient client;
        if (request.requireCert()) {
            try {
                KeyStore keyStore = KeyStore.getInstance("PKCS12");
                ByteArrayInputStream inputStream = new ByteArrayInputStream(certFile);
                try {
                    keyStore.load(inputStream, this.mchId.toCharArray());
                } catch (Exception exception) {
                    throw new WxPayException("加载商务号证书失败，请联系管理员重新配置:" + exception.getMessage());
                } finally {
                    inputStream.close();
                }

                SSLContext sslcontext = org.apache.http.ssl.SSLContexts.custom()
                        .loadKeyMaterial(keyStore, this.mchId
                                .toCharArray())
                        .build();

                SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(
                        sslcontext,
                        new String[]{"TLSv1"},
                        null,
                        new DefaultHostnameVerifier());
                client = HttpClients.custom().setSSLSocketFactory(factory).build();
            } catch (Exception e) {
                throw new WxPayException(e.getMessage());
            }
        } else {
            client = HttpClientBuilder.create()
                    .setConnectionManager(new BasicHttpClientConnectionManager(
                            RegistryBuilder.<ConnectionSocketFactory>create()
                                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                                    .register("https", SSLConnectionSocketFactory.getSocketFactory())
                                    .build(),
                            null,
                            null,
                            null)).build();
        }
        return client;
    }

}
